﻿// Copyright (c) 2010 Xomega.Net. All rights reserved.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Xml;
using System.Xml.Linq;

namespace Xomega.Framework.Lookup
{
    /// <summary>
    /// A class that represents a cache of lookup tables by their types.
    /// It provides a static accessor function to get an instance of lookup cache by cache type.
    /// It also supports loading the lookup data from Xomega enumerations in XML format.
    /// </summary>
    public class LookupCache
    {
        #region Static accessors

        /// <summary>
        /// A constant that represents a global lookup cache that is shared for the whole application.
        /// This is typically a default option for the cache type to get.
        /// </summary>
        public const string Global = "GlobalLookupCache";

        /// <summary>
        /// A constant that represents a lookup cache for the current user session.
        /// </summary>
        public const string User = "UserLookupCache";

        /// <summary>
        /// An instance of the cache provider constructed for the application from the current configuration.
        /// </summary>
        private static ILookupCacheProvider cacheProvider;

        /// <summary>
        /// Gets an instance of a lookup cache of the specified type.
        /// Typically either <see cref="LookupCache.Global"/> or <see cref="LookupCache.User"/> constants are used as a cache type.
        /// </summary>
        /// <param name="cacheType">Cache type.</param>
        /// <returns>An instance of a lookup cache of the specified type or <c>null</c> if no cache can be found.</returns>
        public static LookupCache Get(string cacheType)
        {
            if (cacheProvider == null)
            {
#if SILVERLIGHT
                cacheProvider = new SingletonLookupCacheProvider();
#else
                Type t = typeof(ILookupCacheProvider);
                string cacheProviderClass = ConfigurationManager.AppSettings[t.FullName];
                if (string.IsNullOrEmpty(cacheProviderClass))
                {
                    Trace.TraceWarning("No lookup cache provider is supplied in the configuration. Using default SingletonLookupCacheProvider.");
                    t = typeof(SingletonLookupCacheProvider);
                }
                else t = Type.GetType(cacheProviderClass);
                cacheProvider = Activator.CreateInstance(t) as ILookupCacheProvider;
                if (cacheProvider == null) throw new Exception(
                    "Invalid type supplied in the application configuration for the lookup cache provider: " + cacheProviderClass);
#endif
            }
            return cacheProvider.GetLookupCache(cacheType);
        }
        #endregion

        /// <summary>
        /// A cache of lookup tables by type.
        /// </summary>
        private Dictionary<string, LookupTable> cache = new Dictionary<string, LookupTable>();

        /// <summary>
        /// An internal reader/writer lock to synchronize access to the data.
        /// </summary>
#if !SILVERLIGHT
        private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
#endif
        /// <summary>
        /// Gets a lookup table of the specified type from the cache.
        /// </summary>
        /// <param name="type">Lookup table type.</param>
        /// <returns>A lookup table of the specified type or <c>null</c> if no lookup table can be found.</returns>
        public virtual LookupTable GetLookupTable(string type)
        {
#if SILVERLIGHT
            return cache.ContainsKey(type) ? cache[type] : null;
#else
            rwLock.EnterReadLock();
            try
            {
                return cache.ContainsKey(type) ? cache[type] : null;
            }
            finally
            {
                rwLock.ExitReadLock();
            }
#endif
        }

        /// <summary>
        /// Stores the given lookup table in the current cache under the table's type.
        /// The lookup table and its type should not be <c>null</c>.
        /// </summary>
        /// <param name="table">A lookup table to store.</param>
        public virtual void CacheLookupTable(LookupTable table)
        {
            if (table == null || table.Type == null) return;
#if SILVERLIGHT
            cache[table.Type] = table;
#else
            rwLock.EnterWriteLock();
            try
            {
                cache[table.Type] = table;
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
#endif
        }

        #region LoadFromXml

        /// <summary>
        /// Loads Xomega enumerations in XML format into the current cache from the given stream in case-insensitive manner.
        /// </summary>
        /// <param name="stream">The XML stream to load the lookup data from.</param>
        public void LoadFromXml(Stream stream) { LoadFromXml(stream, false); }

        /// <summary>
        /// Loads Xomega enumerations in Xomega XML format into the current cache from the given stream.
        /// </summary>
        /// <param name="stream">The XML stream to load the lookup data from.</param>
        /// <param name="caseSensitive">A flag indicating whether or not the lookup tables should be case-sensitive.
        /// Typically this is based on whether or not the application database is case-sensitive.</param>
        public void LoadFromXml(Stream stream, bool caseSensitive)
        {
            XDocument doc = XDocument.Load(XmlReader.Create(stream));
            string ns = "http://www.xomega.net/omodel";
            foreach (XElement enm in doc.Descendants(XName.Get("enum", ns)))
            {
                string type = enm.Attribute(XName.Get("name")).Value;
                List<Header> data = new List<Header>();
                foreach(XElement itemNode in enm.Elements(XName.Get("item", ns)))
                {
                    XElement textNode = itemNode.Element(XName.Get("text", ns));
                    string text = (textNode != null) ? textNode.Value : itemNode.Attribute(XName.Get("name")).Value;
                    string id = itemNode.Attribute(XName.Get("value")).Value;
                    Header item = new Header(type, id, text);
                    foreach(XElement prop in itemNode.Elements(XName.Get("prop", ns)))
                    {
                        string pName = prop.Attribute(XName.Get("ref")).Value;
                        string pVal = prop.Attribute(XName.Get("value")).Value;
                        if (item[pName] == null) item[pName] = pVal;
                        else item.AddToAttribute(pName, pVal);
                    }
                    XElement properties = itemNode.Parent.Element(XName.Get("properties", ns));
                    if (properties != null)
                    {
                        foreach (XElement prop in properties.Elements(XName.Get("property", ns)))
                        {
                            XAttribute def = prop.Attribute(XName.Get("default"));
                            string pName = prop.Attribute(XName.Get("name")).Value;
                            if (def != null && item[pName] == null) item[pName] = def.Value;
                        }
                    }
                    data.Add(item);
                }
                CacheLookupTable(new LookupTable(type, data, caseSensitive));
            }
        }
        #endregion
    }
}
